﻿/*===========================================================================================
 * 
 *  Author          : Arturo Salvamante
 * 
 *  File            : NDEF.cs
 * 
 *  Copyright (C)   : Advanced Card System Ltd
 * 
 *  Description     : Contains Methods and Properties for NDEF
 * 
 *  Date            : June 03, 2011
 * 
 *  Revision Traile : [Author] / [Date if modification] / [Details of Modifications done]
 * 
 * 
 * =========================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ACS;

namespace ACS.NDEF
{
    public enum TypeNameFormat
    {
        Empty = 0x00,
        NfcFormWellKnownType = 0x01,
        MediaType = 0x02,
        AbsoluteUri = 0x03,
        NfcFormExternalType = 0x04,
        Unknown = 0x05
    }

    public enum ActionRecord
    {
        DoTheActon = 0x00,
        SaveForLater = 0x01,
        OpenForEditing = 0x02
    }

    public enum URIIdentifierCode
    {
        None = 0x00,
        httpwww = 0x01,
        httpswww = 0x02,
        http = 0x03,
        https = 0x04,
        tel = 0x05,
        mailto = 0x06,
        ftpanonymous = 0x07,
        ftp_ftp = 0x08,
        ftps = 0x09,
        sftp = 0x0A,
        smb = 0x0B,
        nfs = 0x0C,
        ftp = 0x0D,
        dav = 0x0E,
        news = 0x0F,
        telnet = 0x10,
        imap = 0x11,
        rtsp = 0x12,
        urn = 0x13,
        pop = 0x14,
        sip = 0x15,
        sips = 0x16,
        tftp = 0x17,
        btspp = 0x18,
        btl2cap = 0x19,
        btgoep = 0x1A,
        tcpobex = 0x1B,
        irdaobex = 0x1C,
        file = 0x1D,
        urnepcid = 0x1E,
        urnepctag = 0x1F,
        urnepcraw = 0x21,
        urnepc = 0x22,
        urnnfc = 0x23
    }

     public class NdefMessage
    {
        List<NdefRecord> ndefRecords = new List<NdefRecord>();

        public void appendRecord(NdefRecord record)
        {
            ndefRecords.Add(record);
        }

        public void insertRecord(int index, NdefRecord record)
        {
            ndefRecords.Insert(index, record);
        }

        public List<NdefRecord> getRecords()
        {
            return ndefRecords;
        }

        public NdefRecord getRecord(NdefRecordType recordType)
        {
            foreach (NdefRecord r in ndefRecords)
            {
                if (r.recordType.typeName == recordType.typeName &&
                   r.recordType.typeNameFormat == recordType.typeNameFormat)
                {
                    return r;
                }
            }

            return null;
        }

        public int getNumberOfRecords()
        {
            return ndefRecords.Count;
        }

        public byte[] toByteArray()
        {
            byte[] buffer = new byte[0];
            byte[] tmpArray;
            int indx = 0;

            for (int i = 0; i < ndefRecords.Count; i++)
            {
                indx = buffer.Length;

                if (i == 0)
                    ndefRecords[i].messageBegin = true;

                if ((i + 1) == ndefRecords.Count)
                    ndefRecords[i].messageEnd = true;
                    

                tmpArray = ndefRecords[i].encodeToNDEF();

                //Resize destination array to acoomodate new record
                Array.Resize(ref buffer, buffer.Length + tmpArray.Length);
                
                //Copy new  ndef record to byte array
                Array.Copy(tmpArray, 0, buffer, indx, tmpArray.Length);
            }

            return buffer;
        }        
    }

    public class NdefRecordType
    {
        public NdefRecordType(TypeNameFormat format, string name)
        {
            _typeNameFormat = format;
            _typeName = name;
        }

        TypeNameFormat _typeNameFormat;
        public TypeNameFormat typeNameFormat
        {
            get { return _typeNameFormat; }
            set { _typeNameFormat = value; }
        }

        string _typeName;
        public string typeName
        {
            get { return _typeName; }
            set 
            {
                if (value.Trim().Length > 255)
                    throw new Exception("Pay load type is too long");

                _typeName = value.Trim();
            }
        }
    }

    public class NdefRecord
    {
        public NdefRecord(NdefRecordType recordType)
        {
            _messageBegin = false;
            _messageEnd = false;
            _isTerminatingRecordChunk = true;
            _recordType = recordType;
        }

        private bool _messageBegin;
        public bool messageBegin
        {
            get { return _messageBegin; }
            set { _messageBegin = value; }
        }

        private bool _messageEnd;
        public bool messageEnd
        {
            get { return _messageEnd; }
            set { _messageEnd = value; }
        }

        private bool _isTerminatingRecordChunk;
        public bool isTerminatingRecordChunk
        {
            get { return _isTerminatingRecordChunk; }
            set { _isTerminatingRecordChunk = value; }
        }
        
        public bool isShortRecord
        {
            get
            {
                if (payLoad.Length < 256) return true;
                else
                    return false;
            }
        }

        private NdefRecordType _recordType;
        public NdefRecordType recordType
        {
            get { return _recordType; }
            set { _recordType = value; }
        }

        private byte[] _payLoad;
        public byte[] payLoad
        {
            get { return _payLoad; }
            set { _payLoad = value; }
        }

        public string payLoadStr
        {
            get { return ASCIIEncoding.ASCII.GetString(_payLoad); }
            set { _payLoad = ASCIIEncoding.ASCII.GetBytes(value);
            }
        }

        private byte[] _messageID = new byte[0];
        public byte[] messageID
        {
            get { return _messageID; }
            set { _messageID = value; }
        }
        
        public byte[] encodeToNDEF()
        {
            byte ndefHeader = 0;
            byte[] ndef = new byte[0];

            //Set NDEF Header
            if (messageBegin)
                ndefHeader |= 0x80;

            if (messageEnd)
                ndefHeader |= 0x40;

            if (!isTerminatingRecordChunk)
                ndefHeader |= 0x20;

            if (payLoad.Length < 256)
                ndefHeader |= 0x10;

            if (messageID.Length > 0)
                ndefHeader |= 0x08;

            ndefHeader |= (byte)recordType.typeNameFormat;


            ndef = Helper.appendArrays(ndef, ndefHeader);

            //Set Payload Type Length
            ndef = Helper.appendArrays(ndef, (byte)recordType.typeName.Length);

            //Set Payload Length
            if (payLoad.Length < 256)
                ndef = Helper.appendArrays(ndef, (byte)payLoad.Length);
            else
                ndef = Helper.appendArrays(ndef, Helper.intToByte(payLoad.Length));

            //Set Message ID Length
            if (messageID != null && messageID.Length > 0)
                ndef = Helper.appendArrays(ndef, (byte)messageID.Length);

            //Set Payload Type
            ndef = Helper.appendArrays(ndef, ASCIIEncoding.ASCII.GetBytes(recordType.typeName.Trim()));

            //Set Message ID
            if (messageID != null && messageID.Length > 0)
                ndef = Helper.appendArrays(ndef, messageID);

            //Set Payload
            ndef =  Helper.appendArrays(ndef, payLoad);
            
            return ndef;
        }
        
        public void appendPayload(byte[] payLoad)
        {
            int indx = 0;

            if (_payLoad == null)
                _payLoad = new byte[0];

            indx = _payLoad.Length;

            Array.Resize(ref _payLoad, _payLoad.Length + payLoad.Length);

            Array.Copy(payLoad, 0, _payLoad, indx, payLoad.Length);
        }

        public void appendPayload(string payLoad)
        {
            int indx = 0;

            if (payLoad == "")
                return;

            if (_payLoad == null)
                _payLoad = new byte[0];

            indx = _payLoad.Length;

            byte[] buffer = ASCIIEncoding.ASCII.GetBytes(payLoad);

            Array.Resize(ref _payLoad, _payLoad.Length + buffer.Length);

            Array.Copy(buffer, 0, _payLoad, indx, buffer.Length);
        }

        public void appendPayload(byte payLoad)
        {
            appendPayload(new byte[] { payLoad });
        }

        public NdefMessage getNestedNdefMessage()
        {
            if (payLoad == null)
                throw new Exception("Payload is not yet been set");

            return getNestedNdefMessage(0, payLoad);
        }

        public static NdefMessage getNestedNdefMessage(int index, byte[] data)
        {
            NdefMessage ndefMessage = new NdefMessage();
            NdefRecord ndefRecord;
            byte typeNameFormat = 0x00;
            byte typeNameLength = 0x00;
            byte idLength = 0x00;

            int payloadLength = 0x00;
            int currentIndex = 0;

            string typeName = "";

            bool isMessageBeginSet = false;
            bool isMessageEndSet = false;
            bool isIdLengthPresent = false;
            bool isChunkFlagPresent = false;
            bool isShortRecordSet = false;


            if (data.Length <= index)
                throw new Exception("Invalid index");


            for (; index < data.Length;)
            {
                currentIndex = index;

                //Get Type Name Format
                //e.g. NFC Well Know Type = 0x01
                typeNameFormat = (byte)(data[currentIndex] & 0x07);

                if (typeNameFormat != 0x01)
                    throw new Exception("Type Name Format " + ((TypeNameFormat)(typeNameFormat)).ToString() + " is not supported.");
                
                //Check if Message Begin (Bit 7) is set 
                if ((data[currentIndex] & 0x80) != 0x00)
                    isMessageBeginSet = true;
                else
                    isMessageBeginSet = false;

                //Check if Message End (Bit 6) is set
                if ((data[currentIndex] & 0x40) != 0x00)
                    isMessageEndSet = true;
                else
                    isMessageEndSet = false;

                //Check if Chunk Flag (Bit 5) is set
                if ((data[currentIndex] & 0x20) != 0x00)
                    isChunkFlagPresent = true;
                else
                    isChunkFlagPresent = false;

                //Check if Short Record (bit 4) is set
                if ((data[currentIndex] & 0x10) != 0x00)
                    isShortRecordSet = true;
                else
                    isShortRecordSet = false;

                //Check if ID length is set
                if ((data[currentIndex] & 0x08) != 0x00)
                    isIdLengthPresent = true;
                else
                    isIdLengthPresent = false;


                currentIndex += 1;


                //get the type length
                //Refer for Short Record section of NFC Data Exchange Format
                //Technical Specification for more information
                typeNameLength = data[currentIndex];
                currentIndex += 1;

                if (isShortRecordSet)
                {
                    //For Short Record payload, length is 1 byte
                    //Get Payload Length    
                    payloadLength = data[currentIndex];
                    currentIndex += 1;
                }
                else
                {
                    //For Non Short Record payload, length is 4 bytes
                    payloadLength = Helper.byteToInt(data.Skip(currentIndex).Take(4).ToArray());
                    currentIndex += 4;
                }

                if (isIdLengthPresent)
                {
                    //+1 to get ID Length
                    idLength = data[currentIndex];
                    currentIndex += 1;
                }

                //+1 to get Payload Type Name
                //Payload Type Name offset = currentIndex to currentIndex  + Type Name Length
                typeName = ASCIIEncoding.ASCII.GetString(data.Skip(currentIndex).Take(typeNameLength).ToArray());
                currentIndex += typeNameLength;
                
                //Initialize new ndef record object
                ndefRecord = new NdefRecord(new NdefRecordType((TypeNameFormat)typeNameFormat, typeName));
                ndefRecord.messageBegin = isMessageBeginSet;
                ndefRecord.messageEnd = isMessageEndSet;
                ndefRecord.isTerminatingRecordChunk = !isChunkFlagPresent;

                //If ID Length is present get record ID
                if (isIdLengthPresent)
                {
                    ndefRecord.messageID = data.Skip(currentIndex).Take(idLength).ToArray();
                    currentIndex += idLength;
                }

                ndefRecord.payLoad = data.Skip(currentIndex).Take(payloadLength).ToArray();
                currentIndex += payloadLength;
                index = currentIndex;


                ndefMessage.appendRecord(ndefRecord);
            }

            return ndefMessage;
        }
    }

    public class Ndef
    {
        /// <summary>
        /// Encode the following paramter for NDEF SmartPoster
        /// </summary>
        /// <param name="titleLanguage"></param>
        /// <param name="title"></param>
        /// <param name="uriPrefix"></param>
        /// <param name="uri"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static byte[] encodeSmartPoster(string titleLanguage, string title, URIIdentifierCode uriPrefix, string uri, ActionRecord? action)
        {
            byte[] buffer = new byte[0];
            bool useUTF8 = true;
            byte statusByte = 0;

            NdefRecord tmpRecord;
            NdefMessage ndefMessage = new NdefMessage();

            //Encode Title
            if (title.Trim() != "")
            {
                //If UTF 8 set bit 7 to 0
                //If UTF 16 set bit 7 to 1
                if(!useUTF8)
                    statusByte = 0x80;
                   
                //Length of the Language (ISO/IANA language code)
                //ex. "en-US"
                statusByte |= (byte)titleLanguage.Length;

                tmpRecord = new NdefRecord(new NdefRecordType(TypeNameFormat.NfcFormWellKnownType, "T"));
                tmpRecord.messageBegin = true;
                tmpRecord.appendPayload(statusByte);
                tmpRecord.appendPayload(titleLanguage);
                tmpRecord.appendPayload(title);

                ndefMessage.appendRecord(tmpRecord);
            }

            
            //Encode Action            
            if (action != null && action.HasValue)
            {
                tmpRecord = new NdefRecord(new NdefRecordType(TypeNameFormat.NfcFormWellKnownType, "act"));
                tmpRecord.appendPayload((byte)action);
                ndefMessage.appendRecord(tmpRecord);
            }


            //Encode URI
            tmpRecord = new NdefRecord(new NdefRecordType(TypeNameFormat.NfcFormWellKnownType, "U"));

            
            //URI Prefix
            //See URI Record Type Definition 
            //http://www.nfc-forum.org
            tmpRecord.appendPayload((byte)uriPrefix);
            tmpRecord.appendPayload(uri);

            ndefMessage.appendRecord(tmpRecord);


            buffer = ndefMessage.toByteArray();

            //Smart Poster Header
            tmpRecord = new NdefRecord(new NdefRecordType(TypeNameFormat.NfcFormWellKnownType, "Sp"));
            tmpRecord.messageBegin = true;
            tmpRecord.messageEnd = true;
            tmpRecord.appendPayload(buffer);

            buffer = tmpRecord.encodeToNDEF();

            //Create NDEF Message for SmartPoster
            ndefMessage = new NdefMessage();
            ndefMessage.appendRecord(tmpRecord);

            //Encode NDEF Message
            buffer = ndefMessage.toByteArray();

            return buffer;
            
        }

        public static string getURIIdentifierCode(URIIdentifierCode code)
        {
            //For more information please refer to
            // URI Record Type Definition Technical Specification
            switch (code)
            {
                case URIIdentifierCode.http: return "http://";
                case URIIdentifierCode.https: return "https://";
                case URIIdentifierCode.httpwww: return "http://www.";
                case URIIdentifierCode.httpswww: return "https://www.";
                case URIIdentifierCode.tel: return "tel:";
                case URIIdentifierCode.mailto: return "mailto:";
                case URIIdentifierCode.ftp: return "ftp://";
                case URIIdentifierCode.None: return "";
                default: return "Unknown";
            }
        }
    }
}
